package com.alk.frijava.candlestickchart;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javafx.animation.FadeTransition;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.chart.Axis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.shape.Path;
import javafx.util.Duration;


public class CandleStickChart extends XYChart<Number, Number> {

    /**
     * Construct a new CandleStickChart with the given axis.
     *
     * @param xAxis The x axis to use
     * @param yAxis The y axis to use
     */
    public CandleStickChart(Axis<Number> xAxis, Axis<Number> yAxis) {
        super(xAxis, yAxis);
        setAnimated(false);
        xAxis.setAnimated(false);
        yAxis.setAnimated(false);
    }

    /**
     * Construct a new CandleStickChart with the given axis and data.
     *
     * @param xAxis The x axis to use
     * @param yAxis The y axis to use
     * @param data The data to use, this is the actual list used so any changes to it will be reflected in the chart
     */
    public CandleStickChart(Axis<Number> xAxis, Axis<Number> yAxis, ObservableList<Series<Number, Number>> data) {
    	super(xAxis, yAxis);
        setAnimated(false);
        xAxis.setAnimated(false);
        yAxis.setAnimated(false);
        setData(data);
    }
    
    @Override 
    protected void seriesAdded(Series<Number, Number> series, int seriesIndex) {
        // handle any data already in series
        for (int j = 0; j < series.getData().size(); j++) {
            Data<Number, Number> item = series.getData().get(j);
            Node candle = createCandle(seriesIndex, item, j);
            if (shouldAnimate()) {
                candle.setOpacity(0);
                getPlotChildren().add(candle);
                // fade in new candle
                FadeTransition ft = new FadeTransition(Duration.millis(500), candle);
                ft.setToValue(1);
                ft.play();
            } else {
                getPlotChildren().add(candle);
            }
        }
    }

    @Override 
    protected void seriesRemoved(Series<Number, Number> series) {
        // remove all candle nodes
        for (XYChart.Data<Number, Number> d : series.getData()) {
            final Node candle = d.getNode();
            if (shouldAnimate()) {
                // fade out old candle
                FadeTransition ft = new FadeTransition(Duration.millis(500), candle);
                ft.setToValue(0);
                ft.setOnFinished(new EventHandler<ActionEvent>() {

                    @Override
                    public void handle(ActionEvent actionEvent) {
                        getPlotChildren().remove(candle);
                    }
                });
                ft.play();
            } else {
                getPlotChildren().remove(candle);
            }
        }
    }


    /** Called to update and layout the content for the plot */
    @Override 
    protected void layoutPlotChildren() {

    	if (getData() != null) {
    		
        	for (int seriesIndex = 0; seriesIndex < getData().size(); seriesIndex++) {
        		
        		Series<Number, Number> series = getData().get(seriesIndex);
        		Iterator<Data<Number, Number>> iter = super.getDisplayedDataIterator(series);
        		Path seriesPath = null;
        		if (series.getNode() instanceof Path) {
        			seriesPath = (Path) series.getNode();
        			seriesPath.getElements().clear();
        		}
        		while (iter.hasNext()) {
        			Data<Number, Number> item = iter.next();
        			double x = getXAxis().getDisplayPosition(getCurrentDisplayedXValue(item));
        			double y = getYAxis().getDisplayPosition(getCurrentDisplayedYValue(item));
        			Node itemNode = item.getNode();
        			CandleStickValues extra = (CandleStickValues) item.getExtraValue();
        			if (itemNode instanceof Candle && extra != null) {
        				Candle candle = (Candle) itemNode;
        				
        				double open = getYAxis().getDisplayPosition(extra.getOpen());
        				double close = getYAxis().getDisplayPosition(extra.getClose());
        				double high = getYAxis().getDisplayPosition(extra.getHigh());
        				double low = getYAxis().getDisplayPosition(extra.getLow());
                        // 	calculate candle width
        				double candleWidth = -1;
        				if (getXAxis() instanceof NumberAxis) {
        					NumberAxis xa = (NumberAxis) getXAxis();
        					candleWidth = xa.getDisplayPosition(xa.getTickUnit()) * .5; // use 50% width between ticks
        				}
        				// update candle
        				candle.update(y, open, close, high, low, candleWidth);
                        // 	position the candle
        				candle.setLayoutX(x);
        				candle.setLayoutY(y);
        			}
        		}
        	}
        }
    }
        
  
    /**
     * Create a new Candle node to represent a single data item
     *
     * @param seriesIndex The index of the series the data item is in
     * @param item        The data item to create node for
     * @param itemIndex   The index of the data item in the series
     * @return New candle node to represent the give data item
     */
    private Node createCandle(int seriesIndex, final Data item, int itemIndex) {
        Node candle = item.getNode();
        // check if candle has already been created
        if (candle instanceof Candle) {
            ((Candle) candle).setSeriesAndDataStyleClasses("series" + seriesIndex, "data" + itemIndex);
        } else {
            candle = new Candle("series" + seriesIndex, "data" + itemIndex);
            item.setNode(candle);
        }
        return candle;
    }

    /**
     * This is called when the range has been invalidated and we need to update it. If the axis are auto
     * ranging then we compile a list of all data that the given axis has to plot and call invalidateRange() on the
     * axis passing it that data.
     */
    @Override
    protected void updateAxisRange() {
        // For candle stick chart we need to override this method as we need to let the axis know that they need to be able
        // to cover the whole area occupied by the high to low range not just its center data value
        final Axis<Number> xa = getXAxis();
        final Axis<Number> ya = getYAxis();
        List<Number> xData = null;
        List<Number> yData = null;
        if (xa.isAutoRanging()) {
            xData = new ArrayList<Number>();
        }
        if (ya.isAutoRanging()) {
            yData = new ArrayList<Number>();
        }
        if (xData != null || yData != null) {
            for (Series<Number, Number> series : getData()) {
                for (Data<Number, Number> data : series.getData()) {
                    if (xData != null) {
                        xData.add(data.getXValue());
                    }
                    if (yData != null) {
                        CandleStickValues extras = (CandleStickValues) data.getExtraValue();
                        if (extras != null) {
                            yData.add(extras.getHigh());
                            yData.add(extras.getLow());
                        } else {
                        }
                    }
                }
            }
            if (xData != null) {
                xa.invalidateRange(xData);
            }
            if (yData != null) {
                ya.invalidateRange(yData);
            }
        }
    }

	@Override
	protected void dataItemAdded(
			javafx.scene.chart.XYChart.Series<Number, Number> arg0, int arg1,
			javafx.scene.chart.XYChart.Data<Number, Number> arg2) {
		// TODO Auto-generated method stub
		
	}

	@Override
	protected void dataItemChanged(
			javafx.scene.chart.XYChart.Data<Number, Number> arg0) {
		// TODO Auto-generated method stub
		
	}

	@Override
	protected void dataItemRemoved(
			javafx.scene.chart.XYChart.Data<Number, Number> arg0,
			javafx.scene.chart.XYChart.Series<Number, Number> arg1) {
		// TODO Auto-generated method stub
		
	}
}
